#include "stdafx.h"

#include "BufferManager.h"

#include <string.h>	// declares NULL
#include <stdio.h> // printf
#include <stdlib.h> // free

#ifdef _WIN32
#ifdef _DEBUG
	#define _CRTDBG_MAP_ALLOC
	#include <stdlib.h>
	#include <crtdbg.h>
	#undef THIS_FILE
	static char THIS_FILE[] = __FILE__;
	#define new       new( _NORMAL_BLOCK, __FILE__, __LINE__)
	#define malloc(s) _malloc_dbg(s, _NORMAL_BLOCK, __FILE__, __LINE__)
#endif
#endif


namespace tlnet {

// Constructor
BufferManager::BufferManager(HANDLE mutex) {
	this->mutex = mutex;
	this->count = 0;
	this->eol.setOverwrite(true);

	// default eol character setup, watch for traditional eol character
	addEol("\r\n", "");
	addEol("\n", "");

	// blank out link list of Buffers
	this->head = this->tail = NULL;
}

// Deconstructor
BufferManager::~BufferManager() {
	// clear out the current Buffers
	clear();
}


// manage strings which indicate a completed line
// 1) the string indicating a completed line
// 2) the string to replace the matched string with
bool BufferManager::addEol(char *pattern, char *replace) {
	return this->eol.set(pattern, replace);
}

bool BufferManager::delEol(char *pattern) {
	return this->eol.del(pattern);
}

// clear out the current Buffers
void BufferManager::clear() {
	Buffer *tmp;
	while(this->head != NULL) {
		tmp = this->head;
		this->head = this->head->next;

		delete tmp;
	}

	// blank out link list of Buffers
	this->head = this->tail = NULL;
}

bool BufferManager::addBytes(char *bytes, int count) {
	// create new empty Buffer
	Buffer *buffer = new Buffer;
	// store the chars
	buffer->storeBytes(bytes, count);

	// copy off into a buffer so that the 'strchr' is safe
	char *data = _strdup(buffer->data);

	// check if any of the eol strings exist
	Param *Eol;
	eol.reset();
	while(Eol = eol.getNext()) {
		// check for the next EOL pattern
		char *ptr = strstr(data, Eol->data);
		if(ptr != NULL) {
			int size = ptr - data + strlen(Eol->data);
			if(size < count) {
				// there are more bytes, need to form two nodes
				ptr += strlen(Eol->data);

				// copy off this string which has had an EOL detected
				char tmp = ptr[0];	// save the char
				ptr[0] = 0;			// change to end of string with '\0'
				this->addBytes(data, strlen(data));
				// copy off the remaining data, building towards a new string
				ptr[0] = tmp;		// restore the char
				this->addBytes(ptr, strlen(ptr));

				// clean up allocated memory
				delete[] data;
				delete buffer;

				// ok, we exit at this point, our data was handled by the two calls
				return true;
			}

			// flag this Buffer as ready to be used
			buffer->isReady = true;	// TODO (may need to merge though)

			// chop off the EOL
//			count -= strlen(Eol->data);

			// exit out of the Eol while, a result was found
			break;
		}
	}

/*	// Make two nodes if there is an EOL and more bytes afterwards,
	// a single node otherwise.
	char *ptr = strchr(data, '\n');
	if(ptr != NULL) {
		int size = ptr - data + 1;
		if(size < count) {
			// there are more bytes, need to form two nodes
			ptr ++;
			char tmp = ptr[0];
			ptr[0] = 0;
			this->addBytes(data, strlen(data));
			ptr[0] = tmp;
			this->addBytes(ptr, strlen(ptr));

			// clean up allocated memory
			delete[] data;
			delete buffer;

			// ok, we exit at this point, our data was handled by the two calls
			return true;
		}

		// flag this Buffer as ready to be used
		buffer->isReady = true;	// TODO (may need to merge though)

		// chop off the '\n'
		count --;
		// chop off the '\r' if it exists
		if(strchr(data, '\r') != NULL) {
			count --;
		}
	}

	// re-store the chars, this will chop off the '\r\n'
	buffer->storeBytes(bytes, count);
*/
	// re-store the chars, this will chop off the EOL
	buffer->storeBytes(bytes, count);

	// append EOL replacment string, if there is one
	if(buffer->isReady) {	// if set, then Eol has a value
		if(strlen(Eol->value) > 0) {
			Buffer *replace = new Buffer;
			replace->storeBytes(Eol->value, strlen(Eol->value));
			buffer->appendBytes(replace);
		}
	}

	// Request ownership of mutex
	WaitForSingleObject(this->mutex, INFINITE);

	// attach new Buffer to link list
	if(this->head == NULL) {
		// this is our first Buffer
		this->head = this->tail = buffer;
	}
	else {
		// otherwise, attach to end of link list
		this->tail->next = buffer;
		buffer->prev = this->tail;
		buffer->next = NULL;
		this->tail = buffer;
	}

	// remember current overall count
	this->count += count;

	// Need to merge buffers if one doesn't have an EOL
	this->merge(this->head);

	// Release the mutex
	ReleaseMutex(this->mutex);

	// clean up allocated memory
	delete[] data;

	// just always returns true for now
	return true;
}

// within a mutex hold, so everything here has exclusive access
void BufferManager::merge(Buffer *node) {
	// Check for common scenarios which do not result in a merge
	if(node == NULL) {
		return;
	}
	// a 2nd node is required for a merge to be possible
	if(node->next == NULL) {
		return;
	}
	// only merge up to '\n', then start merging using next node
	if(node->isReady) {
		this->merge(node->next);
		return;
	}

	// merge data from 2nd node into first
	node->appendBytes(node->next);

	// Remember a link to the node being removed from the list
	Buffer *tmp = node->next;

	// Adjust links to remove this node
	node->next = tmp->next;

	if(node->next) {
		node->next->prev = node;
	}
	if(tmp == this->tail) {
		this->tail = node;
	}

	// Free up the memory used by no-longer needed node 2
	delete tmp;

	// Additional merges may be required before a '\n' is obtained
	this->merge(node);
}

bool BufferManager::isReady(void) {
	bool isReady = false;

	// Request ownership of mutex
	WaitForSingleObject(this->mutex, INFINITE);

	if(this->head != NULL) {
		isReady = this->head->isReady;
	}

	// Release the mutex
	ReleaseMutex(this->mutex);
	
	return isReady;
}

// returns a pointer to the current available string,
// or NULL if none is available
char* BufferManager::getNext() {
	char *data = NULL;

	// Request ownership of mutex
	WaitForSingleObject(this->mutex, INFINITE);

	if(this->head != NULL) {
		data = this->head->data;
	}

	// Release the mutex
	ReleaseMutex(this->mutex);

	return data;
}

// removes current available string
void BufferManager::pop() {
	long count = 0;

	// Request ownership of mutex
	WaitForSingleObject(this->mutex, INFINITE);

	Buffer *tmp;
	if(this->head != NULL) {
		tmp = this->head;
		this->head = this->head->next;
		if(this->head != NULL) {
			this->head->prev = NULL;
		}
		if(this->tail == tmp) {
			this->tail = this->head;
		}

		count = tmp->count;
		delete tmp;
	}

	// remember current overall count
	this->count -= count;

	// Release the mutex
	ReleaseMutex(this->mutex);
}

// keeps the current available string saved & available at next eol
void BufferManager::push() {
	// Request ownership of mutex
	WaitForSingleObject(this->mutex, INFINITE);

	// If there is data
	if(this->head != NULL) {
		// This works, but we've lost the EOL char, we should put it back on

		// If the string was ready, pretend its not
		this->head->isReady = false;

		// Merge this node with the next, if there is one
		this->merge(this->head);
	}

	// Release the mutex
	ReleaseMutex(this->mutex);
}

// display buffer link-list
void BufferManager::dump() {
	// Request ownership of mutex
	WaitForSingleObject(this->mutex, INFINITE);

	Buffer *slider = this->head;
	printf("[dump]\r\n");
	while(slider != NULL) {
		printf("> '%s'\r\n", slider->data);
		slider = slider->next;
	}

	// Release the mutex
	ReleaseMutex(this->mutex);
}

}